package com.vpclub.utils;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
 * @author : leimingchao
 * @version V1.0
 * @Project: springdemo
 * @Package com.vpclub.utils
 * @Description: 如何将这个类（切面类）中的这些方法（通知方法）动态的在目标方法运行的各个位置切入
 * @date Date : 2021年06月03日 22:45
 */
@Component
@Aspect
@Order(1)
//使用order改变切面的执行顺序，数值越小，优先级越高
public class LogUtils {

    /**
     * 告诉spring每个方法什么时候执行
     *try{
     *      @Before
     *      method.invoke()
     *      @AfterReturning
     *}catch(Exception e){
     *      @AfterThrowing
     *}finally{
     *       @After
     *}
     *
     * 5个通知注解：
     * @Before：在目标方法之前运行                前置通知
     * @After：在目标方法运行结束之后             后置通知
     * @AfterReturning：在目标方法正常返回之后    返回通知
     * @AfterThrowing：在目标抛出异常之后执行     异常通知
     * @Around:环绕                               环绕通知
     *
     *
     *
     *
     * 切入点表达式的写法：
     *     固定格式：execution(访问权限 返回值类型 方法全类名(参数表))
     *
     *     通配符：
     *          * :
     *              1.匹配一个或多个字符
     *                    execution(public int com.vpclub.impl.MyMath*r.*(int,int))
     *              2.匹配任意一个参数：第一个参数是int类型，第二个参数任意类型：(匹配2个参数)
     *                    execution(public int com.vpclub.impl.MyMathCalculator.*(int,*))
     *              3.只能匹配一层路径
     *              4.权限位置 * 不能;权限位置不写就行，public是【可选的】
     *          .. :
     *              1.匹配任意多个参数， 任意类型参数
     *              2.匹配任意多层路径
     *                    execution(public int com.vpclub..MyMath.*(..))
     *  记住2种：
     *         最精确的：execution(public int com.vpclub.impl.MyMathCalculator.add(int,int))
     *         最模糊的：execution(* *.*(..)):千万别写
     *
     *
     *  “&&”、“||”、“!”
     *     &&:我们要切入的位置满足这2个表达式
     *     MyMathCalculator.add(int,double)
     *     execution(public int com.vpclub..MyMath*.*(..))&&execution(* *.*(int,int))
     *
     *     ||:满足任意一个表达式即可
     *     execution(public int com.vpclub..MyMath*.*(..))||execution(* *.*(int,int))
     *
     *     !:只要不是这个位置的都切入
     *     !execution(public int com.vpclub..MyMath*.*(..))
     *
     *
     *
     * 细节四:
     *      我们可以在通知方法运行的时候，拿到目标方法的详细信息
     *          1.我们只需要为通知方法的参数列表上写一个参数：
     *                JoinPoint joinPoint：封装了当前目标方法的详细信息
     *
     *
     * spring对通知方法的要求不严格
     * 唯一要求的是方法的参数列表一定不能乱写？
     *     通知方法是Spring利用反射调用的，每次方法调用得确定这个方法的参数列表的值
     *     参数表上的每一个参数，Spring都得知道是什么？
     *     JoinPoint joinPoint:认识
     *     不知道的参数一定要告诉Spring这是什么？
     *
     * 跟ajax接收服务器数据类似
     * $.post(url,function(abc){
     *     alert(abc);
     * })
     *
     *
     * 抽取可重用的切入点表达式：
     *     1.随便声明一个没有实现的返回void的空方法
     *     2.给方法上标注@Pointcut注解
     */

    @Pointcut(value = "execution(public int com.vpclub.impl.MyMathCalculator.*(..))")
    public void hahaMyPoint() {

    }

    /**
     * 想在执行目标方法之前执行,写切入点表达式
     * execution(访问权限 返回值类型 方法签名)
     */
    @Before("hahaMyPoint()")
    public static void logStart(JoinPoint joinPoint) {
        //获取目标方法运行时使用的参数
        Object[] args = joinPoint.getArgs();
        //获取到方法签名
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println("[LogUtils-前置通知]【"+name+"】方法开始执行，用的参数列表【"+Arrays.asList(args) +"】");
    }

    /**
     * 想在目标方法正常执行完成之后执行
     * 告诉Spring这个result用来接收返回值
     *      returning = "result",方法的变量名和returning的值一样
     */
    @AfterReturning(value = "hahaMyPoint()",returning = "result")
    public static void logReturn(JoinPoint joinPoint,Object result) {
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println("[LogUtils-返回通知]【"+name+"】方法正常执行完成，计算结果是："+result);
    }

    /**
     * 想在目标方法出现异常时执行
     * 告诉Spring这个exception用来接收异常：
     *          throwing = "exception",方法的变量名和throwing的值一样
     * Exception exception:指定通知方法可以接收哪些异常
     */
    @AfterThrowing(value = "hahaMyPoint()",throwing = "exception")
    public static void logException(JoinPoint joinPoint,Exception exception) {
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println("[LogUtils-异常通知]【"+name+"】方法执行出现了异常，异常信息是【"+exception+"】");
    }

    /**
     * 想在目标方法执行结束的时候执行
     *
     */
    @After("hahaMyPoint()")
    private int logEnd(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println("[LogUtils-后置通知]【"+name+"】方法最终结束");
        return 0;
    }

    /**
     *@Around:环绕 :环绕通知
     *      是Spring中强大的通知，动态代理
     *      try{
     *          //前置通知
     *          method.invoke(obj,args)
     *          //返回通知
     *      }catch(Exception e){
     *          //异常通知
     *      }finally{
     *          //后置通知
     *      }
     *
     * 四合一通知就是环绕通知
     *     环绕通知中有一个参数：ProceedingJoinPoint proceedingJoinPoint
     *
     *
     * 环绕通知是优先于普通通知执行，执行顺序
     *     普通通知：[普通前置]========>目标方法==========>[普通后置]===============>[普通方法返回/方法异常]
     *
     *  [普通前置]
     *  {
     *      try{
     *          环绕前置
     *          环绕执行：目标方法
     *          环绕返回
     *      }catch(Exception e){
     *          环绕异常
     *      }finally{
     *          环绕后置
     *      }
     *  }
     *  [普通后置]
     *  [普通方法返回/方法异常]
     *
     *  新的顺序：(环绕前置=======>普通前置)==========>目标方法执行======>环绕正常返回/出现异常=========>环绕后置通知=======>普通后置=======>普通返回或异常
     *
     */
    @Around(value = "hahaMyPoint()")
    public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Object[] args = proceedingJoinPoint.getArgs();
        Object proceed = null;
        try {
            //就是利用反射调用目标方法即可,就是method.invoke(obj,args);
            //@Before
            System.out.println("【环绕前置通知】【"+proceedingJoinPoint.getSignature().getName()+"方法开始】");
            proceed = proceedingJoinPoint.proceed(args);
            //@AfterReturning
            System.out.println("【环绕返回通知】【"+proceedingJoinPoint.getSignature().getName()+"方法正常返回，返回值"+proceed+"】");
        } catch (Exception e) {
            //@AfterThrowing
            System.out.println("【环绕异常通知】【"+proceedingJoinPoint.getSignature().getName()+"方法出现异常，异常信息："+e+"】");
            //注意：为了让外界能知道这个异常，这个异常一定要抛出去
            throw new RuntimeException(e);
        }finally {
            //@After
            System.out.println("【环绕后置通知】【"+proceedingJoinPoint.getSignature().getName()+"】方法结束");
        }
        //反射调用后的返回值也一定返回出去
        return proceed;
    }
}
